package pj.byt;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.oauth2.SaOAuth2Manager;
import cn.dev33.satoken.oauth2.config.SaOAuth2Config;
import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Consts;
import cn.dev33.satoken.oauth2.logic.SaOAuth2Util;
import cn.dev33.satoken.oauth2.model.*;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import pj.model.Result;

import java.net.MalformedURLException;
import java.net.URL;

/**
 * MySaOAuth2Handle
 *
 * @author grl
 * @date 2023/8/24
 */
@Slf4j
@Component
public class MySaOAuth2Handle {

    public MySaOAuth2Handle() {
    }

    /**
     * 授权逻辑处理
     * @param paramObj
     * @return
     */
    public static Object serverRequest(JSONObject paramObj) {
        SaRequest req = SaHolder.getRequest();
        SaResponse res = SaHolder.getResponse();
        SaOAuth2Config cfg = SaOAuth2Manager.getConfig();
        SaClientModel cm;

        if (req.isPath(Api.authorize) && isParam(req,paramObj,SaOAuth2Consts.Param.response_type, SaOAuth2Consts.ResponseType.code)) {
            cm = currClientModel();
            if (!cfg.isCode || !cm.isCode && !cm.isAutoMode) {
                throw new SaOAuth2Exception("暂未开放的授权模式");
            } else {
                // 获取授权code
                return authorize(req, paramObj,res, cfg);
            }

        } else if (req.isPath(Api.token) && isParam(req,paramObj,SaOAuth2Consts.Param.grant_type, SaOAuth2Consts.GrantType.authorization_code)) {
            // 根据code 换取 访问token
            return token(req,paramObj, res, cfg);
        } else if (req.isPath(Api.refresh) && isParam(req,paramObj,SaOAuth2Consts.Param.grant_type, SaOAuth2Consts.GrantType.refresh_token)) {
            // 刷新 token
            return refreshToken(req,paramObj);
        } else if (req.isPath(Api.revoke)) {
            return revokeToken(req,paramObj);
        } else if (req.isPath(Api.doLogin)) {
            // 登陆
            return doLogin(req, res, cfg);
        } else if (req.isPath(Api.doConfirm)) {
            // 确认
            return doConfirm(req);
        } else if (req.isPath(Api.authorize) && isParam(req,paramObj,SaOAuth2Consts.Param.response_type, SaOAuth2Consts.ResponseType.token)) {
            cm = currClientModel();
            if (!cfg.isImplicit || !cm.isImplicit && !cm.isAutoMode) {
                throw new SaOAuth2Exception("暂未开放的授权模式");
            } else {
                return authorize(req,paramObj, res, cfg);
            }
        } else if (req.isPath(Api.token) && isParam(req,paramObj,SaOAuth2Consts.Param.grant_type, SaOAuth2Consts.GrantType.password)) {
            cm = currClientModel();
            if (!cfg.isPassword || !cm.isPassword && !cm.isAutoMode) {
                throw new SaOAuth2Exception("暂未开放的授权模式");
            } else {
                return password(req, res, cfg);
            }
        } else if (req.isPath(Api.client_token) && isParam(req,paramObj,SaOAuth2Consts.Param.grant_type, SaOAuth2Consts.GrantType.client_credentials)) {
            cm = currClientModel();
            if (!cfg.isClient || !cm.isClient && !cm.isAutoMode) {
                throw new SaOAuth2Exception("暂未开放的授权模式");
            } else {
                return clientToken(req, res, cfg);
            }
        } else {
            return "{\"msg\": \"not handle\"}";
        }
    }

    public static Object authorize(SaRequest req,JSONObject paramObj,SaResponse res, SaOAuth2Config cfg) {
        // 判断是否登陆
        if (!StpUtil.isLogin()) {
            // 未登录
            return cfg.notLoginView.get();
        } else {
            RequestAuthModel ra = MySaOAuth2Util.generateRequestAuth(req, paramObj, StpUtil.getLoginId());
            SaOAuth2Util.checkRightUrl(ra.clientId, ra.redirectUri);
            SaOAuth2Util.checkContract(ra.clientId, ra.scope);
            boolean isGrant = SaOAuth2Util.isGrant(ra.loginId, ra.clientId, ra.scope);
            if (!isGrant) {
                // 需要拉起授权
                return cfg.confirmView.apply(ra.clientId, ra.scope);
            } else {
                String redirectUri;
                String clientRedirectUri = paramObj.getString(SaOAuth2Consts.Param.redirect_uri);
                if(StringUtils.isBlank(clientRedirectUri)){
                    clientRedirectUri = req.getParam(SaOAuth2Consts.Param.redirect_uri);
                }
                String serverRedirectUri = paramObj.getString("serverRedirectUri");
                // 校验传入跳转链接是否符合子域名规则
                if(StringUtils.isNotBlank(clientRedirectUri) && StringUtils.isNotBlank(serverRedirectUri) ) {
                    if (StringUtils.isNotBlank(serverRedirectUri)) {
                        String serverDomainName = getHostName(serverRedirectUri);
                        String clientRedirectDomainName = getHostName(clientRedirectUri);
                        if(StringUtils.isBlank(clientRedirectDomainName)){
                            return Result.fail().msg("redirect_uri地址错误！");
                        }
                        String serverFirstLevelDomainName = serverDomainName.substring(serverDomainName.indexOf("."));
                        if(!clientRedirectDomainName.endsWith(serverFirstLevelDomainName)){
                            log.error("redirect_uri {},一级域名 {},服务的配置的域名 {}",clientRedirectUri,serverFirstLevelDomainName,serverDomainName);
                            return Result.fail().msg("redirect_uri不是白名单地址！");
                        }
                    }
                }
                if (SaOAuth2Consts.ResponseType.code.equals(ra.responseType)) {
                    CodeModel codeModel = SaOAuth2Util.generateCode(ra);
                    redirectUri = SaOAuth2Util.buildRedirectUri(ra.redirectUri, codeModel.code, ra.state);
                    // 返回code
                    return Result.ok().data("redirectUri",redirectUri).data("code",codeModel.code);
                } else if (SaOAuth2Consts.ResponseType.token.equals(ra.responseType)) {
                    AccessTokenModel at = SaOAuth2Util.generateAccessToken(ra, false);
                    redirectUri = SaOAuth2Util.buildImplicitRedirectUri(ra.redirectUri, at.accessToken, ra.state);
                    // 返回code
                    return Result.ok().data("redirectUri",redirectUri);
                } else {
                    throw new SaOAuth2Exception("无效response_type: " + ra.responseType);
                }
            }
        }
    }

    public static Object token(SaRequest req,JSONObject paramObj, SaResponse res, SaOAuth2Config cfg) {
        String code = getParam(req,paramObj,SaOAuth2Consts.Param.code);
        String clientId = getParam(req,paramObj,SaOAuth2Consts.Param.client_id);
        String clientSecret = getParam(req,paramObj,SaOAuth2Consts.Param.client_secret);
        String redirectUri = getParam(req,paramObj,SaOAuth2Consts.Param.redirect_uri);
        SaOAuth2Util.checkGainTokenParam(code, clientId, clientSecret, redirectUri);
        AccessTokenModel token = SaOAuth2Util.generateAccessToken(code);
        return SaResult.data(token.toLineMap());
    }

    public static Object refreshToken(SaRequest req,JSONObject paramObj) {
        String clientId = getParam(req,paramObj,SaOAuth2Consts.Param.client_id);
        String clientSecret = getParam(req,paramObj,SaOAuth2Consts.Param.client_secret);
        String refreshToken = getParam(req,paramObj,SaOAuth2Consts.Param.refresh_token);
        SaOAuth2Util.checkRefreshTokenParam(clientId, clientSecret, refreshToken);
        Object data = SaOAuth2Util.refreshAccessToken(refreshToken).toLineMap();
        return SaResult.data(data);
    }

    public static Object revokeToken(SaRequest req,JSONObject paramObj) {
        String clientId = getParam(req,paramObj,SaOAuth2Consts.Param.client_id);
        String clientSecret = getParam(req,paramObj,SaOAuth2Consts.Param.client_secret);
        String accessToken = getParam(req,paramObj,SaOAuth2Consts.Param.access_token);
        if (SaOAuth2Util.getAccessToken(accessToken) == null) {
            return SaResult.ok("access_token不存在：" + accessToken);
        } else {
            SaOAuth2Util.checkAccessTokenParam(clientId, clientSecret, accessToken);
            SaOAuth2Util.revokeAccessToken(accessToken);
            return SaResult.ok();
        }
    }

    public static Object doLogin(SaRequest req, SaResponse res, SaOAuth2Config cfg) {
        return cfg.doLoginHandle.apply(req.getParamNotNull(SaOAuth2Consts.Param.name), req.getParamNotNull("pwd"));
    }

    public static Object doConfirm(SaRequest req) {
        String clientId = req.getParamNotNull(SaOAuth2Consts.Param.client_id);
        String scope = req.getParamNotNull(SaOAuth2Consts.Param.scope);
        Object loginId = StpUtil.getLoginId();
        SaOAuth2Util.saveGrantScope(clientId, loginId, scope);
        return SaResult.ok();
    }

    public static Object password(SaRequest req, SaResponse res, SaOAuth2Config cfg) {
        String username = req.getParamNotNull(SaOAuth2Consts.Param.username);
        String password = req.getParamNotNull(SaOAuth2Consts.Param.password);
        String clientId = req.getParamNotNull(SaOAuth2Consts.Param.client_id);
        String scope = req.getParam(SaOAuth2Consts.Param.scope, "");
        SaOAuth2Util.checkContract(clientId, scope);
        SaHolder.getStorage().set(StpUtil.stpLogic.splicingKeyJustCreatedSave(), "no-token");
        Object retObj = cfg.doLoginHandle.apply(username, password);
        if (!StpUtil.isLogin()) {
            return retObj;
        } else {
            RequestAuthModel ra = new RequestAuthModel();
            ra.clientId = clientId;
            ra.loginId = StpUtil.getLoginId();
            ra.scope = scope;
            AccessTokenModel at = SaOAuth2Util.generateAccessToken(ra, true);
            return SaResult.data(at.toLineMap());
        }
    }

    public static Object clientToken(SaRequest req, SaResponse res, SaOAuth2Config cfg) {
        String clientId = req.getParamNotNull(SaOAuth2Consts.Param.client_id);
        String clientSecret = req.getParamNotNull(SaOAuth2Consts.Param.client_secret);
        String scope = req.getParam(SaOAuth2Consts.Param.scope);
        SaOAuth2Util.checkContract(clientId, scope);
        SaOAuth2Util.checkClientSecret(clientId, clientSecret);
        ClientTokenModel ct = SaOAuth2Util.generateClientToken(clientId, scope);
        return SaResult.data(ct.toLineMap());
    }

    public static SaClientModel currClientModel() {
        String clientId = SaHolder.getRequest().getParam(SaOAuth2Consts.Param.client_id);
        return SaOAuth2Util.checkClientModel(clientId);
    }


    public static boolean isParam( SaRequest req, JSONObject param,String name, String value) {
        String paramValue = param.getString(name);
        if(StringUtils.isBlank(paramValue)){
            paramValue = req.getParam(name);
        }
        return SaFoxUtil.isNotEmpty(paramValue) && paramValue.equals(value);
    }

    public static String getParam( SaRequest req, JSONObject param,String name) {
        String paramValue = param.getString(name);
        if(StringUtils.isBlank(paramValue)){
            paramValue = req.getParam(name);
        }
        return paramValue;
    }



    public static String getHostName(String urlStr) {
        String hostName = "";
        if (StringUtils.isBlank(urlStr)) {
            return hostName;
        }
        try {
            URL url = new URL(urlStr);
            hostName = url.getHost(); // 获取主机名
        } catch (MalformedURLException e) {
            log.error("url 解析失败 {}",urlStr);
        }
        return hostName;
    }

    public static final class Api {
        public static String authorize = "/byt/oauth2/authorize";
        public static String token = "/byt/oauth2/token";
        public static String refresh = "/byt/oauth2/refresh";
        public static String revoke = "/byt/oauth2/revoke";
        public static String client_token = "/byt/oauth2/client_token";
        public static String doLogin = "/byt/oauth2/doLogin";
        public static String doConfirm = "/byt/oauth2/doConfirm";

        public Api() {
        }
    }
}
